#include "method/wireframe.h"
#include "model/scene.h"


#include <QPainter>
#include <QWidget>


void Wireframe::draw(QWidget *container, Scene *scene) {
    QPainter painter(container);

    this->window2viewport(container, scene);

    for ( const Object* object: scene->getObjects() ) {
        if (!object->isVisible()) continue;
        for ( const HalfEdge* e: object->getEdges() ) {
            painter.drawLine(e->begin->x, e->begin->y, e->end->x, e->end->y);
        }
    }
}

void ZBuffer::draw(QWidget *container, Scene *scene) {
    this->window2viewport(container, scene);

    unsigned int container_width = container->width();
    unsigned int container_height = container->height();
    unsigned int data_length = container_width*container_height;

    QRgb background = qRgb(241, 241, 241);
    QRgb black = qRgb(0, 0, 0);

    uchar *pixels_data = new uchar[data_length*4];
    QRgb (*pixels)[container_width] = reinterpret_cast<QRgb(*)[container_width]>(pixels_data);
    std::fill( &pixels[0][0], &pixels[0][0] + data_length, background);


    // GAMBIARRA 1: Faz com que a renderização de preferencia as arestas
    coord gambiarra = 10;

    // GAMBIARRA 2: Declarando a matrix de uma maneira que não buga tudo
    coord *ddata = new coord[data_length];
    coord (*distance)[container_width] = reinterpret_cast<double(*)[container_width]>(ddata);
    std::fill( &distance[0][0], &distance[0][0] + data_length, coord_max );

    for ( const Object* object: scene->getObjects() ) {
        if (!object->isVisible()) continue;
        unsigned int i = 0;
        for ( const Face* face: object->getFaces()) {

            i++;

            int lower = INT_MAX;
            int greater = INT_MIN;

            std::map<int, std::list<Edge>> rows;
            std::list<Edge> buffer;
            const std::vector<const HalfEdge*> &edges = face->edges();

            for ( const HalfEdge* edge: edges ) {
                Vertex begin = *edge->begin,
                        end = *edge->end;

                if ( begin.y > end.y )
                    std::swap(begin, end);

                int ymin = (int)ceil(begin.y);
                int ymax = (int)end.y;

                if ( lower > ymin )
                    lower = ymin;

                if ( greater < ymax )
                    greater = ymax;

                rows[ymin].push_back(Edge(begin, end));
            }

            for ( int row = lower; row <= greater; row++ ) {
                const std::list<Edge> &edges = rows[row];
                buffer.insert(buffer.begin(), edges.begin(), edges.end());

                for( auto i = buffer.begin(); i != buffer.end(); ) {
                    if ( i->max <= row ) {
                        buffer.erase(i++);
                        continue;
                    }
                    i++;
                }

                buffer.sort();

                for( auto i = buffer.begin(); i != buffer.end(); ) {
                    Edge &begin = *(i++);
                    Edge &end = *(i++);

                    coord z = begin.z;
                    coord width = end.x - begin.x;
                    coord iz = (end.z - begin.z)/width;

                    int xmax = end.x;
                    int xmin = begin.x;

                    if ( row > 0 && row < (int)container_height ) {
                        for ( int column = ceil(xmin); column < xmax; column++ ) {
                            if ( column > 0 && column < (int)container_width && distance[row][column] > z ) {
                                distance[row][column] = z;
                                pixels[row][column] = background;
                            }
                            z += iz;
                        }
                    }

                    ++begin;
                    ++end;
                }
            }

            for ( const HalfEdge* edge: edges ) {
                Vertex begin = *edge->begin,
                        end = *edge->end;

                coord edge_width = fabs(end.x - begin.x);
                coord edge_height = fabs(end.y - begin.y);

                if ( edge_width >= edge_height ) {
                    if ( begin.x > end.x )
                        std::swap(begin, end);
                    coord y = begin.y;
                    coord z = begin.z - gambiarra;
                    coord iy = (end.y-begin.y)/edge_width;
                    coord iz = (end.z-begin.z)/edge_width;

                    for ( int column = ceil(begin.x); column <(int)end.x; column++ ) {
                        int row = (int)round(y);
                        if ( row > 0 && row < (int)container_height && column > 0 && column < (int)container_width && distance[row][column] >= z ) {
                            distance[row][column] = z;
                            pixels[row][column] = black;
                        }
                        y += iy;
                        z += iz;
                    }
                } else {
                    if ( begin.y > end.y )
                        std::swap(begin, end);
                    coord x = begin.x;
                    coord z = begin.z - gambiarra;
                    coord ix = (end.x-begin.x)/edge_height;
                    coord iz = (end.z-begin.z)/edge_height;

                    for ( int row = ceil(begin.y); row < (int)end.y; row++ ) {
                        int column = (int)round(x);
                        if ( row > 0 && row < (int)container_height && column > 0 && column < (int)container_width && distance[row][column] >= z ) {
                            distance[row][column] = z;
                            pixels[row][column] = black;
                        }
                        z += iz;
                        x += ix;
                    }
                }
            }
        }
    }

    QImage image = QImage(pixels_data, container_width, container_height, QImage::Format_RGB32);
    QPainter painter(container);
    painter.drawImage(0, 0, image);

    delete ddata;
    delete pixels_data;
}

